/**
 * RTCPage 使用rtc内存缓存文本内容
 */
#include <Hardware.h>

#include <RTCPage.h>
#include <dirent.h>
#include <NetTime.h>
#include <RX8025.h>
#include <FS.h>
#include <u8Matrix.h>

// 横屏
// #define SCREEN_ROWS 10 // 屏幕字符行数 (SCREEN_HEIGHT/LINE_HEIGHT) - 2
// #define SCREEN_COLS 50 // 屏幕英文字符列数 (SCREEN_WIDTH/TEXT_SIZE) - 4

// 竖屏
#define SCREEN_ROWS 18 // 17//19 // 屏幕字符行数 (SCREEN_HEIGHT/LINE_HEIGHT) - 2
#define SCREEN_COLS 29 // 屏幕英文字符列数 (SCREEN_WIDTH/TEXT_SIZE) - 4, 设置为比实际大1

/*----------------------------------------------------------------
以下内容均缓存在RTC内存中，以便系统被唤醒后快速响应，提高用户体验，
而且可以避免频繁读取SD卡
----------------------------------------------------------------*/
RTC_DATA_ATTR uint8_t rtcViewType;             // 当前视图
RTC_DATA_ATTR int8_t rtcPgIdx;                 // 当前页
RTC_DATA_ATTR int8_t focusLine = -1;           // 焦点行
RTC_DATA_ATTR uint8_t tmStep = 0;              // 词条复习步骤
RTC_DATA_ATTR int16_t maxPgNo = -1;            // 已生成索引的最大页号
RTC_DATA_ATTR int16_t tailPgNo = INT16_MAX;    // 尾页号
RTC_DATA_ATTR int fileLength;                  // 文件长度
RTC_DATA_ATTR char rtcName[MAX_NAME_LENGTH];   // 文本文件名称
RTC_DATA_ATTR PAGE_DATA rtcPage[MAX_RTC_PAGE]; // 页信息
RTC_DATA_ATTR char rtcBuffer[MAX_RTC_BUFFER];  // 文本缓存
RTC_DATA_ATTR char *rtcTail = rtcBuffer;       // 缓存至

char const lastReadFile[] = "/sd/lastRead.cfg";
char const dirPgxFile[] = "/sd/__dir__.pgx";

char wrongFormat[] = "\n只支持UTF-8格式文本文件";
char noTerm[] = "\n没有需要记忆的词条";
char dirString[] = "文件目录";
char idxName[MAX_NAME_LENGTH]; // 索引文件名称
char ctxInfo[33];
char nullString[1] = {0};
char cnLevel[] = "⑴ "; // 记忆级别

int16_t lastPgNo = -1; // 进度已变化词条页号，将被写入SD卡

#define PAGE_UP -1 // 0x80
#define PAGE_DN 1  // 0x40

#define MAX_LEVEL 11
// 艾宾浩斯记忆法曲线日期：5分钟 30分钟 12小时 1天 2天 4天 7天 15天 30天 90天 180天
// 据此定义从本次学习完成开始，下次复习间隔时间(以分钟为单位)
int addons[] = {5, 30, 12 * 60, 1440, 1440, 2 * 1440, 3 * 1440, 8 * 1440, 15 * 1440, 60 * 1440, 90 * 1440};

#define seekLineEnd(f) while (fgetc(f) != '\n' && !feof(f)) // 读取文件直至换行或文件尾

#define diffMinute(t1, t2) (difftime(t1, t2) / 60)          // 比较两个时间差的分钟数
#define nowMinute() (rxTime.getTime() / 60)                 // 取当前分钟数
#define nextTime(level) (nowMinute() + addons[level])       // 返回本级词条下次应复习的时间（以分钟为单位）
#define readIndexFile(n, p, s) indexFile((n), (p), (s), 1)  // 读取文件索引
#define writeIndexFile(n, p, s) indexFile((n), (p), (s), 0) // 更新文件索引

#define readNowPgNo(p) indexFile(0, (p), 2, 1)  // 读取当前阅读位置
#define writeNowPgNo(p) indexFile(0, (p), 2, 0) // 更新当前阅读位置

char getViewType()
{
    return rtcViewType;
}

void setViewType(char type)
{
    rtcViewType = type;
    rtcPgIdx = 0;
    maxPgNo = -1;
    tailPgNo = INT16_MAX;
    rtcTail = rtcBuffer;
}

// 检查指定页号是否已在缓存内
bool termInBuffer(int no)
{
    for (int i = 0; i < MAX_RTC_PAGE; i++)
    {
        if (rtcPage[i].no == no)
        {
            return true;
        }
    }
    return false;
}

// 从字符串src中复制出第x行字符串到dest，成功则返回真
bool copyIndexString(char *dest, char *src, int index)
{
    char *e = NULL;
    do
    {
        e = strchr(src, '\n');
        if (!index)
        {
            if (e != NULL)
            {
                *e = 0;
            }

            strcpy(dest, src);

            if (e != NULL)
            {
                *e = '\n';
            }
            return true;
        }
        src = e + 1;
        index--;
    } while (e != NULL);

    return false;
}

// 清除指定索引号缓存的文本
void cutRTCBuffer(int index)
{
    if (rtcPage[index].no < INT16_MAX)
    {
        rtcPage[index].no = INT16_MAX;

        // 回收指定项文本空间
        int len = strlen(rtcPage[index].title) + strlen(rtcPage[index].content) + 2;
        char *dest = rtcPage[index].title;
        char *src = dest + len;
        memmove(dest, src, rtcTail - src);

        rtcTail -= len;
        for (int i = 0; i < MAX_RTC_PAGE; i++)
        {
            if (rtcPage[i].no < INT16_MAX && rtcPage[i].title >= src)
            {
                rtcPage[i].title -= len;
                rtcPage[i].content -= len;
            }
        }
    }
}

// 将偏移量写入或读出页索引文件
bool indexFile(int pgNo, void *buf, int size, int side)
{
    int count = 0;

    FILE *fp = fopen(idxName, side > 0 ? "rb" : "rb+");
    if (fp)
    {
        if (!fseek(fp, pgNo * sizeof(long), SEEK_SET))
        {
            count = side > 0 ? fread(buf, size, 1, fp) : fwrite(buf, size, 1, fp);
        }

        fclose(fp);
    }

    return count == 1;
}

void saveProcess()
{
    if (getViewType() == VT_READ)
    {
        writeNowPgNo(&rtcPage[rtcPgIdx].no);
    }
    if (getViewType() == VT_TERM && lastPgNo >= 0)
    {
        FILE *fp = fopen(idxName, "rb+");
        if (fp)
        {
            for (int i = 0; i < MAX_RTC_PAGE; i++)
            {
                if (rtcPage[i].no == lastPgNo)
                {
                    fseek(fp, rtcPage[i].no * sizeof(PAGE_DATA) + 2, SEEK_SET);
                    fwrite(&(rtcPage[i].level), sizeof(int16_t), 1, fp);
                    fwrite(&(rtcPage[i].nextMinute), sizeof(long), 1, fp);

                    lastPgNo = -1;
                    break;
                }
            }
            fclose(fp);
        }
    }
}

// 句尾标点符号
char const *enSplits = ",.;:!)}]?";
char const *cnSplits = "，。；：！）｝］’”？";

/**
 * @brief 检查最后读取的是否句尾标点符号
*/
bool isEndSplit(int u, char *buf, int i)
{
    if (u > 1) // 是否在utf-8句尾符号表内
    {
        char const *p = cnSplits;
        while (*p)
        {
            if (p[0] == buf[i - 3] && p[1] == buf[i - 2] && p[2] == buf[i - 1])
            {
                return true;
            }
            p += 3;
        }
    }
    else // 是否在ascii句尾符号表内
    {
        char const *p = enSplits;
        while (*p)
        {
            if (*p == buf[i - 1])
            {
                return true;
            }
            p++;
        }
    }

    return false;
}

/**
 * @brief 从文件中读入utf8字符串
 * @param fp 文件指针
 * @param buf 读入缓存
 * @param pRows 每页行数
 * @param mode 模式 0 一般模式 1 遇换行符结束 2 遇换行符结束，首行已预设级别(占3个字节)
 * @return int 本页行数
*/
int fileToBuf(FILE *fp, char *buf, int pRows, int mode = 0)
{
    bool lastReturn = false;
    int i = 0, row = 0, b, u;

    b = mode < 2 ? 0 : 3;
    while (!feof(fp))
    {
        buf[i] = fgetc(fp);
        if(buf[i] == '\r') // windows格式txt换行有\r\n, 要忽略\r
        {
            continue;
        }

        if(lastReturn) // 文本阅读状态下，插入换行符后又读到换行符，要忽略
        {
            lastReturn = false;
            if(buf[i] == '\n' && mode == 0)
            {
                continue;
            }
        }

        // 计算utf-8字符占用多少个字节
        u = 0;
        for (char c = buf[i]; u <= 6 && (c & 0x80); u++, c <<= 1)
            ;

        i++;

        if (u > 1) // utf8字符,读出其余字节
        {
            for (int t = u; t > 1; t--)
            {
                buf[i++] = fgetc(fp);
            }
        }
        else // ascii字符
        {
            u = 1;
            if (buf[i - 1] == '\n')
            {
                if (i == 1 || (i > 2 && buf[i - 2] == '\n' && buf[i - 3] == '\n')) // 忽略首行换行符，或多于两个换行
                {
                    i--;
                    continue;
                }

                if (mode != 0) // 遇换行符即完成一页
                {
                    break;
                }
            }
            else if (buf[i - 1] < 32) // 少于32的符号字库内没有，显示时将被替换为空格
            {
                buf[i - 1] = ' ';
            }
        }

        if (buf[i - 1] == '\n')
        {
            row++;
            b = 0;
        }
        else
        {
            b += u > 1 ? 2 : 1;

            if (b >= SCREEN_COLS) // 超过行宽了
            {
                if(isEndSplit(u, buf, i)) // 检查最后字符是否句尾符，是则行尾增加换行符
                {
                    buf[i++] = '\n';
                    b = 0; // 长度回零
                    lastReturn = true;
                }
                else // 否则将最后字符推至下一行
                {
                    for (int t = 0; t < u; t++)
                    {
                        buf[i - t] = buf[i - t - 1];
                    }
                    buf[i - u] = '\n';
                    i++;

                    b = u > 1 ? 2 : 1;
                }

                row++;
            }
        }

        if (row >= pRows)
        {
            if (b) // 读多了，需要回退文件指针
            {
                i -= u;
                fseek(fp, -u, SEEK_CUR);
            }
            break;
        }
    }

    buf[i] = 0;

    // 若非满页或空页，行数需加1
    return row == pRows || i == 0 ? row : row + 1;
}

void saveLastRead(uint8_t type, char *fName)
{
    if (sdMount(true))
    {
        FILE *fp = fopen(lastReadFile, "wt+");
        if (fp)
        {
            fprintf(fp, "%d\n", type);
            fprintf(fp, "%s\n", fName);
            fclose(fp);
        }
        sdMount(false);
    }
}

uint8_t readLastRead(char *fName)
{
    int type = 0;

    if (sdMount(true))
    {
        FILE *fp = fopen(lastReadFile, "rt");
        if (fp)
        {
            fscanf(fp, "%d\n", &type);
            fscanf(fp, "%s\n", fName);
            fclose(fp);
        }
        sdMount(false);
    }

    return type;
}

RTCPage::RTCPage(bool init)
{
    if (init)
    {
        if (sdMount(true))
        {
            strcpy(rtcName, "/sd");
            strcpy(idxName, dirPgxFile);

            FILE *idx = fopen(idxName, "w+");
            fclose(idx);

            initPage();

            sdMount(false);
        }
        focusLine = 0;
    }
    else
    {
        strcpy(idxName, dirPgxFile);
    }
}

RTCPage::RTCPage(bool init, char *fileName)
{
    if (init)
    {
        if (sdMount(true))
        {
            if (getViewType() == VT_READ || getViewType() == VT_TERM)
            {
                strcpy(rtcName, "/sd/");
                strcpy(rtcName + 4, fileName);
            }

            strcpy(idxName, rtcName);
            strcpy(idxName + strlen(idxName) - 3, getViewType() == VT_READ ? "pgx" : "tmx");

            if (getViewType() == VT_READ || getViewType() == VT_TERM)
            {
                FILE *idx = fopen(idxName, "a+");
                if (maxPgNo < 0 && !fseek(idx, 0, SEEK_END)) // 更新已生成索引的最大页号
                {
                    maxPgNo = ftell(idx) / (getViewType() == VT_READ ? sizeof(long) : sizeof(PAGE_DATA));
                }
                fclose(idx);
            }

            initPage();

            sdMount(false);
        }
        focusLine = -1;
    }
    else
    {
        strcpy(idxName, rtcName);
        strcpy(idxName + strlen(idxName) - 3, getViewType() == VT_READ ? "pgx" : "tmx");
    }
}

void RTCPage::initPage()
{
    int no = 0;

    tailPgNo = INT16_MAX;

    for (int index = 0; index < MAX_RTC_PAGE; index++)
    {
        rtcPage[index].no = INT16_MAX;
    }

    if (getViewType() == VT_READ)
    {
        // 读文件长度
        FILE *fp = fopen(rtcName, "rb");
        fseek(fp, 0, SEEK_END);
        fileLength = ftell(fp);
        fclose(fp);

        readNowPgNo(&no); // 读入上次阅读页号
    }
    if (no > 0) // 为保证正常预读，从前一页开始装入
    {
        load(no - 1, MAX_RTC_PAGE);
        rtcPgIdx = 1;
    }
    else
    {
        load(no, MAX_RTC_PAGE);
        rtcPgIdx = 0;
    }
}

bool RTCPage::append(PAGE_DATA *buf, int side = 1)
{
    // 若空间不足，则返回
    if ((rtcTail + strlen(buf->title) + strlen(buf->content) + 2) - rtcBuffer > MAX_RTC_BUFFER)
    {
        dbLog("Memory full!");
        return false;
    }

    // 查找插入点
    int index = 0;
    while (index < MAX_RTC_PAGE && rtcPage[index].no != INT_MAX)
    {
        if (rtcPage[index].no == buf->no) // 不能有相同的页
        {
            return false;
        }

        if (side && rtcPage[index].no > buf->no) // 根据页号决定插入位置
        {
            break;
        }

        index++;
    }

    // 根据插入确定移出项
    if (index < MAX_RTC_PAGE)
    {
        // 回收尾项文本空间
        cutRTCBuffer(MAX_RTC_PAGE - 1);
        // 向右平移各项
        if (index < MAX_RTC_PAGE - 1) // 需要平移
        {
            memmove(rtcPage + index + 1, rtcPage + index, sizeof(PAGE_DATA) * (MAX_RTC_PAGE - 1 - index));
        }
        rtcPgIdx++;
    }
    else
    {
        // 回收首项文本空间
        cutRTCBuffer(0);
        // 向左平移各项
        memmove(rtcPage, rtcPage + 1, sizeof(PAGE_DATA) * (MAX_RTC_PAGE - 1));
        rtcPgIdx--;

        index--; // 即 index = MAX_RTC_PAGE - 1
    }

    memcpy(&rtcPage[index], buf, sizeof(PAGE_DATA));

    int len = strlen(buf->title) + 1;
    memcpy(rtcTail, buf->title, len);
    rtcPage[index].title = rtcTail;
    rtcTail += len;

    len = strlen(buf->content) + 1;
    memcpy(rtcTail, buf->content, len);
    rtcPage[index].content = rtcTail;
    rtcTail += len;

    return true;
}

void RTCPage::checkPreLoad()
{
    int16_t prePgNo = -1; // 预读起始页
    uint8_t prePgCount;   // 预读页数

    if (readSide == PAGE_UP) // 向首页方向读
    {
        readSide = 0;
        // 为防止预读失败，代码改以1代替0，给第二次预读机会
        // if (rtcPgIdx == 0 && rtcPage[rtcPgIdx].no > 0)
        if (rtcPgIdx <= 1 && rtcPage[rtcPgIdx].no > 0)
        {
            // 当前页为首页，则预装入上多页
            prePgNo = rtcPage[rtcPgIdx].no;
            prePgCount = PRE_UP_PAGE;
            prePgNo -= prePgCount;
            if (prePgNo < 0)
            {
                prePgCount += prePgNo;
                prePgNo = 0;
            }
        }
    }

    if (readSide == PAGE_DN) // 向尾页方向读
    {
        readSide = 0;
        // 为防止预读失败，改代码，给第二次预读机会
        // if (rtcPgIdx == MAX_RTC_PAGE - 1 && rtcPage[rtcPgIdx].no < tailPgNo)
        if (rtcPgIdx >= MAX_RTC_PAGE - 2 && rtcPage[rtcPgIdx].no < tailPgNo)
        {
            // 当前页为尾页，则预装入下多页
            prePgNo = rtcPage[rtcPgIdx].no + 1;
            prePgCount = PRE_DN_PAGE;
        }
    }

    if (sdMount(true))
    {
        // 保存阅读进度或复习进度
        saveProcess();

        if (prePgNo >= 0) // 预读新内容
        {
            // 尝试读3次
            int t = 0;
            do
            {
                if (load(prePgNo, prePgCount))
                {
                    break;
                }
                t++;
                dbLogValue("Pre load time: ", t);
                delay(10);
            } while (t < SD_RETRY_TIME);

            if (t == SD_RETRY_TIME)
            {
                dbLog("Pre load failed!");
            }
        }

        sdMount(false);
    }
}

bool RTCPage::load(int pgNo, int pgCount)
{
    bool ret = false;

    switch (getViewType())
    {
    case VT_DIR_F:
    case VT_DIR_T:
        ret = loadDir(pgNo, pgCount);
        break;
    case VT_READ:
        ret = loadFile(pgNo, pgCount);
        break;
    case VT_TERM:
        ret = loadTerm(pgCount);
        break;
    }
    return ret;
}

bool RTCPage::loadDir(int pgNo, int pgCount)
{
    long offset;

    if (pgNo <= 0)
    {
        offset = 0;
    }
    else
    {
        if (!readIndexFile(pgNo, &offset, 4))
        {
            return false;
        }
    }

    DIR *dir;

    // 装入SD卡根目录文件列表
    dir = opendir(rtcName);
    if (!dir)
    {
        return false;
    }

    PAGE_DATA pg;
    File file;
    int rCount = 0;
    struct dirent *ptr;
    long *pOffset = new long[pgCount + 1];
    pg.content = new char[1024];

    seekdir(dir, offset);
    while (rCount < pgCount)
    {
        pOffset[rCount] = telldir(dir);
        pg.nextMinute = pOffset[rCount]; // 借用来储存该页在目录中的偏移

        int row = 0;
        char *pStr = pg.content;
        while ((ptr = readdir(dir)) != NULL)
        {
            if (strcmp(ptr->d_name, ".") == 0 || strcmp(ptr->d_name, "..") == 0)
            {
                continue;
            }

            if (!strstr(ptr->d_name, ".txt")) // 只列出文件名后缀为.txt的文件
            {
                continue;
            }
            strcpy(pStr, ptr->d_name);
            pStr += strlen(ptr->d_name);
            (*pStr) = '\n';
            pStr++;
            if (++row >= SCREEN_ROWS)
            {
                break;
            }
        }

        if (row > 0) // 若有内容，增加一页
        {
            pg.no = pgNo + rCount;
            pg.title = nullString; // rtcName;
            pg.level = row;        // 借用来储存本页行数(列目录专用)
            (*pStr) = 0;
            append(&pg);

            rCount++;
        }

        if (ptr == NULL)
        {
            tailPgNo = pg.no;
            break;
        }
    }

    // 写入索引文件
    if (pg.no >= maxPgNo)
    {
        pOffset[rCount] = telldir(dir);
        writeIndexFile(pgNo, pOffset, (rCount + 1) * sizeof(long));
        maxPgNo = pg.no;
    }

    delete pOffset;
    delete pg.content;

    closedir(dir);
    return rCount > 0;
}

bool RTCPage::loadFile(int pgNo, int pgCount)
{
    long offset;

    if (pgNo <= 0)
    {
        offset = 3;
    }
    else
    {
        if (!readIndexFile(pgNo, &offset, 4))
        {
            dbLog("readIndexFile failed!");
            return false;
        }
    }

    // 打开文件并读入
    FILE *fp = fopen(rtcName, "rb");
    if (!fp)
    {
        dbLog("fopen failed!");
        return false;
    }

    PAGE_DATA pg;

    int rCount = 0;
    if (fgetc(fp) == 0xEF && fgetc(fp) == 0xBB && fgetc(fp) == 0xBF)
    {
        long *pOffset = new long[pgCount + 1];
        pg.content = new char[1024];

        fseek(fp, offset, SEEK_SET);
        while (rCount < pgCount)
        {
            pOffset[rCount] = ftell(fp);
            pg.nextMinute = pOffset[rCount]; // 借用来储存该页在文件中偏移
            if (fileToBuf(fp, pg.content, SCREEN_ROWS) > 0)
            {
                pg.no = pgNo + rCount;
                pg.title = nullString; // rtcName;
                append(&pg);
                rCount++;
            }

            if (feof(fp))
            {
                tailPgNo = pg.no;
                break;
            }
        }

        // 写入索引文件
        if (pg.no >= maxPgNo)
        {
            pOffset[rCount] = ftell(fp);
            writeIndexFile(pgNo, pOffset, (rCount + 1) * sizeof(long));
            maxPgNo = pg.no;
        }

        delete pOffset;
        delete pg.content;
    }
    else // 不支持非UTF-8文本文件
    {
        pg.no = 0;
        pg.title = rtcName;
        pg.content = wrongFormat;
        append(&pg);
        rCount++;
        maxPgNo = pg.no;
        tailPgNo = pg.no;
    }

    fclose(fp);

    return rCount > 0;
}

bool RTCPage::loadTerm(int pgCount)
{
    FILE *fp, *tp;
    int newPgNo = 0;
    int pageOffset = 3;
    bool pageEof = false;

LOAD_TERM:
    fp = fopen(idxName, "rb+");
    if (!fp)
    {
        return false;
    }

    // 装入全部索引页
    fseek(fp, 0, SEEK_END);
    int size = ftell(fp) / sizeof(PAGE_DATA);
    if (size)
    {
        tp = fopen(rtcName, "rb");
        if (tp)
        {
            fseek(fp, 0, SEEK_SET);
            PAGE_DATA *pData = new PAGE_DATA[size];
            if (fread(pData, sizeof(PAGE_DATA), size, fp))
            {
                long now = nowMinute();
                char *title = new char[256];
                char *content = new char[1024];
                newPgNo = pData[size - 1].no + 1; // 新编号为最后编号加1

                // 检查词本是否已被全部索引
                fseek(tp, pData[size - 1].contentOffset, SEEK_SET);
                seekLineEnd(tp);
                pageOffset = ftell(tp);
                pageEof = feof(tp);

                // 级别自高向低搜索
                for (int level = MAX_LEVEL; level >= 0 && pgCount > 0; level--)
                {
                    for (int i = 0; i < size && pgCount > 0; i++)
                    {
                        if (pData[i].nextMinute <= now && pData[i].level == level && !termInBuffer(pData[i].no))
                        {
                            fseek(tp, pData[i].titleOffset, SEEK_SET);
                            strcpy(title, cnLevel); // 标题前缀加记忆级别
                            title[2] += pData[i].level;
                            int tRows = fileToBuf(tp, title + 4, SCREEN_COLS, 2);
                            //fileToBuf(tp, title + 4, INT_MAX, 3);
                            fseek(tp, pData[i].contentOffset, SEEK_SET);
                            fileToBuf(tp, content, SCREEN_COLS - tRows - 1, 1);
                            // fileToBuf(tp, content, INT_MAX);
                            pData[i].title = title;
                            pData[i].content = content;
                            if (append(&pData[i], 0))
                            {
                                pgCount--;
                            }
                        }
                    }
                }
                delete content;
                delete title;
            }
            delete pData;
            fclose(tp);
        }
    }

    fclose(fp);
    if (pgCount <= 0 || pageEof)
    {
        return true;
    }

    if (appendTerm(newPgNo, pageOffset))
    {
        goto LOAD_TERM;
    }

    // 没有需要记忆的词条
    PAGE_DATA pg;
    pg.no = newPgNo;
    pg.title = rtcName;
    pg.content = noTerm;
    append(&pg);
    maxPgNo = pg.no;
    tailPgNo = pg.no;

    return false;
}

bool RTCPage::appendTerm(int pgNo, long pgOffset)
{
    int i = 0;
    PAGE_DATA data;

    // 索引中未找到足够的词条，从词条文件中增加
    FILE *fp = fopen(idxName, "rb+");
    if (fp)
    {
        fseek(fp, 0, SEEK_END);

        // 生成新词条索引，每次生成MAX_RTC_PAGE
        FILE *tp = fopen(rtcName, "rb");
        if (tp)
        {
            fseek(tp, pgOffset, SEEK_SET);
            for (; i < MAX_RTC_PAGE && !feof(tp); i++)
            {
                data.no = pgNo + i;
                data.level = 0;
                data.nextMinute = 0;

                data.titleOffset = ftell(tp);
                seekLineEnd(tp);
                data.contentOffset = ftell(tp);
                seekLineEnd(tp);

                fwrite(&data, sizeof(PAGE_DATA), 1, fp);
            }
            fclose(tp);
        }
        fclose(fp);
    }

    return i > 0;
}

bool RTCPage::step(int side)
{
    if (focusLine >= 0) // 若有焦点行
    {
        if (side > 0)
        {
            if (focusLine < rtcPage[rtcPgIdx].level - 1)
            {
                focusLine++; // 未到页尾，直接向下移动焦点行
                return true;
            }
            if (rtcPage[rtcPgIdx].no != tailPgNo)
            {
                focusLine = 0; // 已到页尾但未到文件尾，将切换到下一页，焦点行为零
            }
        }

        if (side < 0)
        {
            if (focusLine >= 1)
            {
                focusLine--; // 未到页头直接向上移动焦点行
                return true;
            }
            if (rtcPage[rtcPgIdx].no != 0)
            {
                focusLine = SCREEN_ROWS - 1; // 已到页头但未到文件头，切换到上一页，焦点行为最后一行
            }
        }
    }

    // 词条记忆,由回想到确认
    if (getViewType() == VT_TERM)
    {
        if (!tmStep) // 显示内容
        {
            tmStep++;
            return true;
        }

        if (side > 0)
        {
            // 正常完成复习，升一级、变更时间
            rtcPage[rtcPgIdx].nextMinute = nextTime(rtcPage[rtcPgIdx].level);
            if (rtcPage[rtcPgIdx].level < MAX_LEVEL)
            {
                rtcPage[rtcPgIdx].level++;
            }
            tmStep = 0;
        }
        else
        {
            // 很不熟悉，降一级
            if (rtcPage[rtcPgIdx].level > 0)
            {
                rtcPage[rtcPgIdx].level--;
            }
            rtcPage[rtcPgIdx].nextMinute = nextTime(rtcPage[rtcPgIdx].level);
            tmStep = 0;
            side = 1; // 记忆词条不能向后退
        }
        // 储存页号以用于保存进度
        lastPgNo = rtcPage[rtcPgIdx].no;
    }

    int index = rtcPgIdx + side;

    if (index < 0 || index > MAX_RTC_PAGE - 1 || rtcPage[index].no >= INT16_MAX)
    {
        dbLogValue("index:", index);
        if (index >= 0 && index < MAX_RTC_PAGE)
        {
            dbLogValue("pageNo", rtcPage[index].no);
        }
        return false;
    }

    rtcPgIdx = index;

    // 设置状态标志以便系统空闲时预读入
    readSide = (side > 0) ? PAGE_DN : PAGE_UP;
    return true;
}

int RTCPage::getFocusLine()
{
    return focusLine;
}

bool RTCPage::copyFocusString(char *str)
{
    return copyIndexString(str, rtcPage[rtcPgIdx].content, focusLine);
}

char *RTCPage::readCtxInfo()
{
    if (focusLine >= 0)
    {
        return dirString;
    }
    else
    {
        int len = 0;
        char *str = rtcName + 4;
        while (len < 8)
        {
            if ((*str) & 0x80)
            {
                if (len + 2 > 12)
                {
                    break;
                }
                str += 3;
                len += 2;
            }
            else
            {
                str++;
                len++;
            }
            if ((*str) == '.')
            {
                break;
            }
        }

        len = str - rtcName - 4;
        strncpy(ctxInfo, rtcName + 4, len);
        if (getViewType() == VT_READ)
        {
            sprintf(ctxInfo + len, " | %.2f%%%", (float)rtcPage[rtcPgIdx].nextMinute * 100 / fileLength);
        }
        else
        {
            ctxInfo[len] = 0;
        }

        return ctxInfo;
    }
}

char *RTCPage::readTitle()
{
    return rtcPage[rtcPgIdx].title;
}

char *RTCPage::readContent()
{
    // 词条记忆回想环节不显示内容
    return (getViewType() == VT_TERM && !tmStep) ? nullString : rtcPage[rtcPgIdx].content;
}